package pl.genzy.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;

import javax.swing.JFileChooser;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.swing.JTextArea;
import javax.swing.JTable;
import java.awt.Font;

import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.ListSelectionModel;

import pl.genzy.converter.Converter;

public class LogicRiddle {

	private JFrame frameLogicRiddleGenerator;
	private JScrollPane scrollPane;
	private JPanel panel;
	private JTable table;
	private JFileChooser fileChooser;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					LogicRiddle window = new LogicRiddle();
					window.frameLogicRiddleGenerator.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public LogicRiddle() {
		initialize();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frameLogicRiddleGenerator = new JFrame();
		frameLogicRiddleGenerator.setTitle("Logic riddle generator");
		frameLogicRiddleGenerator.setBounds(100, 100, 623, 398);
		frameLogicRiddleGenerator.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JMenuBar menuBar = new JMenuBar();
		frameLogicRiddleGenerator.setJMenuBar(menuBar);
		
		JMenu menuFile = new JMenu("File");
		menuBar.add(menuFile);
		
		JMenuItem menuItemOpenImage = new JMenuItem("Open image");
		menuItemOpenImage.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				action(false);
			}
		});
		menuFile.add(menuItemOpenImage);
		
		JMenuItem menuItemOpenAndResolve = new JMenuItem("Open Image and resolve");
		menuItemOpenAndResolve.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				action(true);
			}
		});
		menuFile.add(menuItemOpenAndResolve);
		
		mntmSaveAsImage = new JMenuItem("Save as image...");
		mntmSaveAsImage.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				saveToFile();
			}
		});
		menuFile.add(mntmSaveAsImage);
		
		scrollPane = new JScrollPane();
		frameLogicRiddleGenerator.getContentPane().add(scrollPane, BorderLayout.CENTER);
		
		panel = new JPanel();
		
		table = new JTable();
		table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		table.setFont(new Font("Tahoma", Font.PLAIN, 9));
		table.setFillsViewportHeight(true);
		panel.add(table);
		
		scrollPane.setViewportView(panel);
		
		console = new JTextArea();
		console.setWrapStyleWord(true);
		console.setRows(4);
		
		frameLogicRiddleGenerator.getContentPane().add(console, BorderLayout.SOUTH);
		
		consoleScroll = new JScrollPane(console, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		
		frameLogicRiddleGenerator.getContentPane().add(consoleScroll, BorderLayout.SOUTH);
	}
	
	private void saveToFile() {
		Dimension size = table.getSize();
		if(size.width == 0
			|| size.height == 0) {
			writeln("Nothing to export.");
			return;
		}
		
		fileChooser = new JFileChooser();
		fileChooser.setDialogTitle("Set name of file with .gif extension");
		
		if(fileChooser.showSaveDialog(frameLogicRiddleGenerator) != JFileChooser.APPROVE_OPTION)
			return;
		File file = fileChooser.getSelectedFile();
		String fullName = file.getName(); 
		
		write("Saving " + fullName + "... ");
		
		BufferedImage myImage = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
		Graphics2D g2 = myImage.createGraphics();
		table.paint(g2);
		
		try {
			ImageIO.write(myImage, "GIF", file);
			writeln("done");
		} catch (IOException ioe) {
			writeln("write error: " + ioe.getMessage());
		} catch(IllegalArgumentException iae) {
			writeln("dev error: bad argument");
		}
	}
	
	private void action(boolean showSolution) {
		fileChooser = new JFileChooser();
		
		if(fileChooser.showOpenDialog(frameLogicRiddleGenerator) != JFileChooser.APPROVE_OPTION)
			return;
		
		File file = fileChooser.getSelectedFile();
		
		BufferedImage image = null;
		try {
			image = ImageIO.read(file);
		} catch (IOException e) {
			writeln("error: " + e.getMessage());
		}
		
		write("Image loaded " + file.getName());
		write("; height: " + image.getHeight());
		writeln("; width: " + image.getWidth());
		
		try {
			prepareTable(image, showSolution);
		} catch(Exception ex) {
			writeln("error: " + ex.getMessage());
		}
	}
	
	private int DefaultColumnWidth = 18;
	private JMenuItem mntmSaveAsImage;
	private JTextArea console;
	private JScrollPane consoleScroll;
	private void prepareTable(BufferedImage image, boolean showSolution) {
		Converter converter = new Converter(image);

		table.setModel(new RiddleTableModel(converter));
		table.setDefaultRenderer(Object.class, new RiddleTableRenderer(converter, showSolution));

		for(int i = 0; i < table.getColumnCount(); i++) {
			table.getColumnModel().getColumn(i).setPreferredWidth(DefaultColumnWidth);
			table.getColumnModel().getColumn(i).setWidth(DefaultColumnWidth);
		}
	}
	
	@SuppressWarnings("serial")
	private class RiddleTableModel extends AbstractTableModel {
		
		private Converter converter;
		
		public RiddleTableModel(Converter converter) {
			if(converter == null)
				throw new NullPointerException("dev: no converter");
			
			this.converter = converter;
		}
		
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			//area of solution - solution is displayed optionally by renderer
			if(rowIndex >= converter.getMaxHorizontalHeight()
				&& columnIndex >= converter.getMaxVeriticalHeight())
				return ""; //btw, why Java String doesn't have String.empty() or sth like that? :|
			
			//left top corner of the riddle
			if(rowIndex < converter.getMaxHorizontalHeight()
				&& columnIndex < converter.getMaxVeriticalHeight())
				return "";
			
			//numbers of the top of the riddle
			if(rowIndex < converter.getMaxHorizontalHeight())
			{
				if(rowIndex < converter.getHorizontalNumbers().get(columnIndex - converter.getMaxVeriticalHeight()).size())
					return converter.getHorizontalNumbers().get(columnIndex - converter.getMaxVeriticalHeight()).get(rowIndex);
			}
			
			//numbers on the left of the riddle
			if(columnIndex < converter.getMaxVeriticalHeight())
			{
				if(columnIndex < converter.getVeriticalNumbers().get(rowIndex - converter.getMaxHorizontalHeight()).size())
					return converter.getVeriticalNumbers().get(rowIndex - converter.getMaxHorizontalHeight()).get(columnIndex);
			}
			
			return "";
		}
		
		@Override
		public void setValueAt(Object aValue, int rowIndex, int columnIndex) { }
		
		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return false;
		}
		
		@Override
		public int getRowCount() {
			return converter.getDimension().height + converter.getMaxHorizontalHeight();
		}
		
		@Override
		public String getColumnName(int columnIndex) { return ""; }
		
		@Override
		public int getColumnCount() {
			return converter.getDimension().width + converter.getMaxVeriticalHeight();
		}
	}
	
	enum BorderRendererMode { Left, Bottom, Both }
	
	@SuppressWarnings("serial")
	private class RiddleTableRenderer implements TableCellRenderer {

		//attributes
		private Converter converter;
		private DefaultTableCellRenderer renderer;
		private boolean showSolution;
		
		private CustomBorder leftBorder, bottomBorder, bothBorder;
		
		private Color lightColor;
		
		//constructor
		public RiddleTableRenderer(Converter converter, boolean showSolution) {
			if(converter == null)
				throw new NullPointerException("dev: converter not set");
			
			this.converter = converter;
			
			leftBorder = new CustomBorder(BorderRendererMode.Left);
			bottomBorder = new CustomBorder(BorderRendererMode.Bottom);
			bothBorder = new CustomBorder(BorderRendererMode.Both);
			
			renderer = new DefaultTableCellRenderer();
			lightColor = new Color(235, 235, 235);
			
			this.showSolution = showSolution;
		}
		
		//border renderer subclass
		class CustomBorder extends AbstractBorder {
			
			private BorderRendererMode mode;
			
			public CustomBorder(BorderRendererMode mode) {
				this.mode = mode;
			}
			
			@Override
			public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
				super.paintBorder(c, g, x, y, width, height);
				
				switch(mode) {
					case Left: g.drawLine(x, y, x, y + height); break;
					case Bottom: g.drawLine(x, y + height - 1, x + width, y + height - 1); break;
					case Both: {
						g.drawLine(x, y, x, y + height);
						g.drawLine(x, y + height - 1, x + width, y + height - 1);
					} break;
				}
			}
		}
		
		private Border defaultBorder;
		private void setBorder(DefaultTableCellRenderer component, Border newBorder) {
			if(defaultBorder == null)
				defaultBorder = component.getBorder();
			
			if(newBorder == null)
				component.setBorder(defaultBorder);
			else
				component.setBorder(newBorder);
		}
		
		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
			
			Component component = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
			DefaultTableCellRenderer stronglyTypedComponent = (DefaultTableCellRenderer)component; 
			
			stronglyTypedComponent.setText(" " + value.toString());
			
			//gray
			if(row < converter.getMaxHorizontalHeight()
				|| column < converter.getMaxVeriticalHeight()) {
				
				component.setBackground(lightColor);
				
				//top gray
				if(row < converter.getMaxHorizontalHeight()) {
					if(((column - converter.getMaxVeriticalHeight()) % 5) == 0
						&& column >= converter.getMaxVeriticalHeight()) {
						if(row == converter.getMaxHorizontalHeight() - 1)
							setBorder(stronglyTypedComponent, bothBorder);
						else
							setBorder(stronglyTypedComponent, leftBorder);
						
						return component;
					}
					
					if(row == converter.getMaxHorizontalHeight() - 1) {
						setBorder(stronglyTypedComponent, bottomBorder);
						return component;
					}
				}
				
				//left gray
				if(row > converter.getMaxHorizontalHeight()
					&& column <= converter.getMaxVeriticalHeight()) {
					
					if(((row - converter.getMaxHorizontalHeight() + 1) % 5) == 0) {
						setBorder(stronglyTypedComponent, bottomBorder);
						return component;
					}
				}
				
				return component;
			}
			component.setBackground(Color.WHITE);
			setBorder(stronglyTypedComponent, null);
			
			//main table
			//both
			if(((column - converter.getMaxVeriticalHeight()) % 5) == 0
				&& ((row - converter.getMaxHorizontalHeight() + 1) % 5) == 0) {
				setBorder(stronglyTypedComponent, bothBorder);
				
				//solution
				if(showSolution) {
					if(converter.getSolution()[converter.xy2idx(row - converter.getMaxHorizontalHeight(), column - converter.getMaxVeriticalHeight())])
						component.setBackground(Color.LIGHT_GRAY);
					else
						component.setBackground(Color.WHITE);
				}
				
				return component;
			}
			
			//left
			if(((column - converter.getMaxVeriticalHeight()) % 5) == 0) {
				setBorder(stronglyTypedComponent, leftBorder);
				
				//solution
				if(showSolution) {
					if(converter.getSolution()[converter.xy2idx(row - converter.getMaxHorizontalHeight(), column - converter.getMaxVeriticalHeight())])
						component.setBackground(Color.LIGHT_GRAY);
					else
						component.setBackground(Color.WHITE);
				}
				
				return component;
			}
			
			//bottom
			if(((row - converter.getMaxHorizontalHeight() + 1) % 5) == 0) {
				setBorder(stronglyTypedComponent, bottomBorder);
				
				//solution
				if(showSolution) {
					if(converter.getSolution()[converter.xy2idx(row - converter.getMaxHorizontalHeight(), column - converter.getMaxVeriticalHeight())])
						component.setBackground(Color.LIGHT_GRAY);
					else
						component.setBackground(Color.WHITE);
				}
				
				return component;
			}
			//---
			
			//solution
			if(showSolution) {
				if(converter.getSolution()[converter.xy2idx(row - converter.getMaxHorizontalHeight(), column - converter.getMaxVeriticalHeight())])
					component.setBackground(Color.LIGHT_GRAY);
				else
					component.setBackground(Color.WHITE);
			}
			
			return component;
		}
		
	}
	
	private void write(String string) {
		console.append(string);
	}
	
	private void writeln(String string) {
		write(string + System.getProperty("line.separator"));
	}
}
